#include "web/WebPageQuery.hh"
#include "nlohmann/json.hpp"

#include <fstream>
#include <iostream>
#include <sstream>
#include <map>
#include <set>
#include <math.h>


using std::ifstream;
using std::istringstream;
using std::map;
using std::set;
using std::cout;
using namespace tinyxml2;

double cosl(const vector<double> &vec1, const vector<double> &vec2);
std::size_t length(const std::string &str);

WebPageQuery::WebPageQuery(SplitTool *tool)
    : _tool(tool){
    loadLibrary();
}

vector<string> WebPageQuery::doQuery(const string &query){

    vector<string> tem = _tool->cut(query);
    vector<string> queryWords;
    for(auto&elem : tem){
        if(length(elem.c_str()) != elem.size()){
            queryWords.push_back(elem);
        }
    }
    return queryWords;
}
void WebPageQuery::loadLibrary(){
    ifstream ifs;
    auto& conf_map = Configuration::createConfig()->getConfigMap();
    ifs.open(conf_map["newoffset"][0]);
    while(ifs){
        int id;
        size_t pos;
        size_t offset;
        ifs >> id;
        ifs >> pos;
        ifs >> offset;
        _offsetLib.insert({id, {pos, offset}});
    }
    ifs.close();
    // 读倒排索引文件

    ifs.open(conf_map["invertIndex"][0]);
    // 先读一个string
    // 读一个char
    // 读一个int, 读一个 double
    string line;
    while(getline(ifs, line)){
        // cout << line << "\n";
        istringstream iss(line);
        while(iss){
            string word;
            iss >> word;
            _invertIndexTable[word];
            // cout << word << "\n";
            char ch;
            iss >> ch;

            while(iss){
                int docid;
                iss >> docid;
                iss >> ch;

                double weight;
                iss >> weight;
                iss >> ch;
                _invertIndexTable[word].insert({docid, weight});
            }
        }
    }  
    // NOTE:这里大小没问题，但是输出有问题


    // 加载网页
    loadPage();
}
vector<double> WebPageQuery::getQueryWordsWeightVector(const vector<string> &queryWords){
    vector<double> weightVec;
    double N = 1;
    map<string, int> m;
    for(auto &word : queryWords){

        if(m.find(word) == m.end()){
            m[word] = 1;
        }else{
            m[word]++;
        }
    }

    double sum = 0;
    for(auto &word : queryWords){
        double TF = m[word];
        double DF = 1.0;
        double IDF = log2(N / (DF + 1));
        sum += pow(TF * IDF, 2);
    }

    double norm = sqrt(sum);
    for(auto &word : queryWords){
        double TF = m[word];
        double DF = 1.0;
        double IDF = log2(N / (DF + 1));
        double w = (TF * IDF) / norm;
        weightVec.push_back(w);
    }
    return weightVec;
}
bool WebPageQuery::executeQuery(const vector<string> &queryWords, vector<pair<int, vector<double>>> &resultVec){

    // 执行查询
    map<int, map<string, double>> docidSet; // 全集
    for(auto &word : queryWords){
        auto& s = _invertIndexTable[word];
        for(auto& elem : s){
            docidSet[elem.first].insert({word, elem.second});
        }
    }
    for(auto& word : queryWords){
        auto& s = _invertIndexTable[word];
        set<int> temp; // 某个单词的集合
        for(auto& elem : s){
            temp.insert(elem.first);
        }

        // 求交集
        for(auto it = docidSet.begin(); it != docidSet.end();){
            if(temp.find(it->first) == temp.end()){
                it = docidSet.erase(it);
            }else{
                ++it;
            }
        }
    }
    // cout << "docidSet的大小： " << docidSet.size() << "\n";
    // 不存在这样的文章
    if(docidSet.size() == 0){return false;}

    // 创建网页向量
    for(auto& elem : docidSet){
        vector<double> vec;
        for(auto& word : queryWords){
            vec.push_back(elem.second[word]);
        }
        
        resultVec.push_back({elem.first, vec});
    }
    // 按照COS排序
    // 余弦相似算法排序
    return true;
}
string WebPageQuery::createJson(vector<int> &docidVec, const vector<string> &queryWords){

    nlohmann::json json_array = nlohmann::json::object();
	for(auto& elem: docidVec){
		json_array.push_back({_pageLib[elem]._title, _pageLib[elem]._url});
	}
    return json_array.dump();
}
string WebPageQuery::returnNoAnswer(){
    return "{\"code\": 0, \"msg\": \"没有找到\"}";
}


vector<int> WebPageQuery::CosSort(vector<pair<int, vector<double>>>& resultVec, const vector<string>& queryWords){
    priority_queue<pair<int, double>, vector<pair<int, double>>, MyCompare1> _resultQueue;

    for(auto& elem : resultVec){
        _resultQueue.push({elem.first, cosl(elem.second, getQueryWordsWeightVector(queryWords))});
    }
    _resultQueue.pop();
    int count = _resultQueue.size() > 10 ? 10 : resultVec.size();


    vector<int> ret;
    
    auto& conf_map = Configuration::createConfig()->getConfigMap();

    ifstream ifs(conf_map["newripepage"][0]);
    for(int i = 0; i < count; i++){
        ret.push_back(_resultQueue.top().first);
        
        _resultQueue.pop();
    }
    return ret;
}



double cosl(const vector<double> &vec1, const vector<double> &vec2){
    double sum1 = 0;
    for(auto& elem : vec1){
        sum1 = sum1 + pow(elem, 2);
    }

    double sum2 = 0;
    for(auto& elem : vec2){
        sum2 = sum2 + pow(elem, 2);
    }

    double sum3 = 0;
    for(size_t i = 0; i < vec1.size(); i++){
        sum3 += vec1[i] * vec2[i];
    }


    return (sum3 / (sqrt(sum1) * sqrt(sum2)));
}



size_t nBytesCode(const char ch)
{
	if(ch & (1 << 7))
	{
		int nBytes = 1;
		for(int idx = 0; idx != 6; ++idx)
		{
			if(ch & (1 << (6 - idx)))
			{
				++nBytes;	
			}
			else
				break;
		}
		return nBytes;
	}
	return 1;
}  
std::size_t length(const std::string &str)
{
	std::size_t ilen = 0;
	for(std::size_t idx = 0; idx != str.size(); ++idx)
	{
		int nBytes = nBytesCode(str[idx]);
		idx += (nBytes - 1);
		++ilen;
	}
	return ilen;
}


void WebPageQuery::loadPage(){

    auto& conf_map = Configuration::createConfig()->getConfigMap();

    XMLDocument doc;
    doc.LoadFile(conf_map["newripepage"][0].c_str());

    XMLElement* root = doc.RootElement();
    for(XMLElement* p = root; p != nullptr; p = p->NextSiblingElement()){
        string docid = p->FirstChildElement("docid")->GetText();
        string title = p->FirstChildElement("title")->GetText();
        string url = p->FirstChildElement("url")->GetText();

        // 添加网页
        _pageLib.insert({atoi(docid.c_str()), WebPage(title, url)});
    }
}